﻿using System;
using System.ComponentModel;
using ActionLogic;
using BehaviorModel.Block.Finish;
using BehaviorModel.Block.RandomGenerator;
using Model;
using Model.Block;
using log4net;

namespace BehaviorModel.Block.ServiceChannel
{
    /// <summary>
    /// Service device type.
    /// </summary>
    public enum ServiceChannelType
    {
        Loss,
        Queue,
        Waiting
    }

    /// <summary>
    /// Describes implementation of behavior model of service channel in QSM.
    /// </summary>
    public class ServiceChannel : IServiceChannel
    {
        #region Fields

        private static readonly ILog logger = LogManager.GetLogger(typeof (ServiceChannel));

        private Request _servedRequest;
        private ServiceChannelType _channelType;
        private RandomGeneratorType _generatorType;
        private IReceiverBlock _receiverBlock;
        private IRequestBuffer _innerBuffer;

        #endregion

        #region Properties

        #region IBlock

        public Guid Id { get; private set; }

        public BlockType Type
        {
            get { return BlockType.ServiceChannel; }
        }

        #endregion

        #region IExecutable

        public Action Execute
        {
            get { return Service; }
        }

        public double Priority
        {
            get { return Storage.WorkingTime + Storage.WaitingTime; }
        }

        public bool IsBackground
        {
            get { return false; }
        }

        public event Action<IExecutable> OnActionExecuted;

        #endregion

        #region IServiceChannel

        public ServiceChannelStorage Storage { get; private set; }

        public bool IsBusy { get; private set; }

        public ServiceChannelType ChannelType
        {
            get { return _channelType; }
            set
            {
                if (_channelType == value)
                {
                    return;
                }

                _channelType = value;
                switch (value)
                {
                    case ServiceChannelType.Waiting:
                        MaxQueueLength = 0;
                        break;
                    case ServiceChannelType.Loss:
                        MaxQueueLength = 1;
                        break;
                    default:
                        MaxQueueLength = 10;
                        break;
                }
                RaisePropertyChanged("ChannelType");
            }
        }

        public int MaxQueueLength
        {
            get { return _innerBuffer.MaxCapacity; }
            set { _innerBuffer.MaxCapacity = value; }
        }

        public int QueueLength
        {
            get { return _innerBuffer.Count; }
        }

        public ChoosingDiscipline Discipline
        {
            get { return _innerBuffer.Discipline; }
            set { _innerBuffer.Discipline = value; }
        }

        public IRandomGenerator ServiceGenerator { get; private set; }

        public RandomGeneratorType GeneratorType
        {
            get { return _generatorType; }
            set
            {
                if (_generatorType == value)
                {
                    return;
                }

                _generatorType = value;
                ServiceGenerator = RandomGeneratorFactory.GetInstance().Create(_generatorType);
                RaisePropertyChanged("GeneratorType");
            }
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #endregion

        #region Constructor

        public ServiceChannel()
        {
            Id = Guid.NewGuid();
            Storage = new ServiceChannelStorage();

            _innerBuffer = new RequestBuffer();
            _innerBuffer.OnRequestRejected += RejectRequest;
            _innerBuffer.OnRequestAdded += Service;

            GeneratorType = RandomGeneratorType.Poisson;
            ServiceGenerator = RandomGeneratorFactory.GetInstance().Create(_generatorType);
            Discipline = ChoosingDiscipline.Fifo;
            ChannelType = ServiceChannelType.Loss;
            MaxQueueLength = 0;
            IsBusy = false;
        }

        #endregion

        #region Methods

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #region IBlock

        public void Initialize()
        {
            _innerBuffer.Clear();
            _innerBuffer.OnRequestAdded += Service;
            Storage = new ServiceChannelStorage();
            IsBusy = false;

            logger.Info("INIT " + GetType());
        }

        #endregion

        #region ISenderBlock

        public void ConnectReceiverBlock(IReceiverBlock block)
        {
            _receiverBlock = block;
        }

        public void DisconnectReceiverBlock(IReceiverBlock receiverBlock)
        {
            _receiverBlock = null;
        }

        public void Send(Request request)
        {
            if (_receiverBlock == null) 
            {
                StatisticController.GetInstance().Collector.ReceiveServiced(request);
                return;
            }
            _receiverBlock.Receive(request);
        }

        #endregion

        #region IReceiverBlock

        public void Receive(Request request)
        {
            logger.Info("Id Receive: " + Id + " in " + request.InitTime + request.CommonServiceTime + request.CommonWaitingTime);
            if (ChannelType == ServiceChannelType.Loss && IsBusy)
            {
                RejectRequest(request);
                logger.Info("Id Reject : " + Id + " in " + request.InitTime + request.CommonServiceTime + request.CommonWaitingTime);
                return;
            }

            UpdateWaitingTime(request);
            _innerBuffer.Add(request);
        }

        #endregion

        public void Service()
        {
            if (CheckEmptyBuffer())
            {
                //logger.Info("Service Buffer Is Empty!");
                return;
            }



            _servedRequest = _innerBuffer.Get();
            if (_servedRequest != null)
            {
                logger.Info("SERVICE START");
                logger.Info("SC.TIME = " + (Storage.WorkingTime + Storage.WaitingTime));
                logger.Info("SC.WORK = " + Storage.WorkingTime + " SC.WAIT = " + Storage.WaitingTime);
                logger.Info("REQ.TIME = " +
                    (_servedRequest.InitTime + _servedRequest.CommonServiceTime + _servedRequest.CommonWaitingTime));

                var servicedTime = ServiceRequest();
                logger.Info("SERVICED TIME = " + servicedTime);

                logger.Info("SERVICE CONTINUES");
                logger.Info("SC.TIME = " + (Storage.WorkingTime + Storage.WaitingTime));
                logger.Info("SC.WORK = " + Storage.WorkingTime + " SC.WAIT = " + Storage.WaitingTime);
                logger.Info("REQ.TIME = " +
                    (_servedRequest.InitTime + _servedRequest.CommonServiceTime + _servedRequest.CommonWaitingTime));

                Send(_servedRequest);
                _innerBuffer.UpdateWaitingQueue(servicedTime);
            }


            if (OnActionExecuted != null)
            {
                OnActionExecuted(this);
            }
        }

        private bool CheckEmptyBuffer()
        {
            if (_innerBuffer.IsEmtpy())
            {
                IsBusy = false;
                _innerBuffer.OnRequestAdded += Service;

                logger.Info("SC FREE");
                return true;
            }
            IsBusy = true;
            _innerBuffer.OnRequestAdded -= Service;
            return false;
        }

        private double ServiceRequest()
        {
            IsBusy = true;

            var servicedTime = ServiceGenerator.GetNext();
            _servedRequest.CommonServiceTime += servicedTime;
            _servedRequest.CommonWaitingTime += _servedRequest.LastQueueWaitingTime;

            Storage.WorkingTime += servicedTime;
            Storage.WaitingTime = _servedRequest.InitTime + _servedRequest.CommonServiceTime +
                                  _servedRequest.CommonWaitingTime - Storage.WorkingTime;
            Storage.CountOfServiced++;

            return servicedTime;
        }

        private void RejectRequest(Request request)
        {
            StatisticController.GetInstance().Collector.ReceiveRejected(request);
            Storage.CountOfRejected++;
        }

        private void UpdateWaitingTime(Request request)
        {
            request.LastQueueWaitingTime = 0;

            var requestLiveTime = request.InitTime + request.CommonServiceTime + request.CommonWaitingTime;
            var scLiveTime = Storage.WorkingTime + Storage.WaitingTime;

            if (requestLiveTime > scLiveTime)
            {
                Storage.WaitingTime += requestLiveTime - scLiveTime;
            }
            else if (requestLiveTime < scLiveTime)
            {
                request.LastQueueWaitingTime = scLiveTime - requestLiveTime;
            }
        }

        #endregion
    }
}